/*
 * @(#)DomReader.java
 *
 *
 * ver 1.0 Jul 16, 2007 ggranum
 */
package org.testfire.util;

import dom.ParserWrapper;
import java.io.*;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import org.apache.xerces.dom.DOMOutputImpl;
import org.w3c.dom.*;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.*;
import org.xml.sax.SAXException;

/**
 * Most of the code here was stolen shamelessly from samples.dom.Counter.java
 */
public class DomReader
{

    //
    // Constants
    //

    // feature ids

    /**
     * Namespaces feature id (http://xml.org/sax/features/namespaces).
     */
    protected static final String NAMESPACES_FEATURE_ID =
        "http://xml.org/sax/features/namespaces";

    /**
     * Validation feature id (http://xml.org/sax/features/validation).
     */
    protected static final String VALIDATION_FEATURE_ID =
        "http://xml.org/sax/features/validation";

    /**
     * Schema validation feature id (http://apache.org/xml/features/validation/schema).
     */
    protected static final String SCHEMA_VALIDATION_FEATURE_ID =
        "http://apache.org/xml/features/validation/schema";

    /**
     * Schema full checking feature id (http://apache.org/xml/features/validation/schema-full-checking).
     */
    protected static final String SCHEMA_FULL_CHECKING_FEATURE_ID =
        "http://apache.org/xml/features/validation/schema-full-checking";

    /**
     * Honour all schema locations feature id (http://apache.org/xml/features/honour-all-schemaLocations).
     */
    protected static final String HONOUR_ALL_SCHEMA_LOCATIONS_ID =
        "http://apache.org/xml/features/honour-all-schemaLocations";

    /**
     * Validate schema annotations feature id (http://apache.org/xml/features/validate-annotations).
     */
    protected static final String VALIDATE_ANNOTATIONS_ID =
        "http://apache.org/xml/features/validate-annotations";

    /**
     * Dynamic validation feature id (http://apache.org/xml/features/validation/dynamic).
     */
    protected static final String DYNAMIC_VALIDATION_FEATURE_ID =
        "http://apache.org/xml/features/validation/dynamic";

    /**
     * XInclude feature id (http://apache.org/xml/features/xinclude).
     */
    protected static final String XINCLUDE_FEATURE_ID =
        "http://apache.org/xml/features/xinclude";

    /**
     * XInclude fixup base URIs feature id (http://apache.org/xml/features/xinclude/fixup-base-uris).
     */
    protected static final String XINCLUDE_FIXUP_BASE_URIS_FEATURE_ID =
        "http://apache.org/xml/features/xinclude/fixup-base-uris";

    /**
     * XInclude fixup language feature id (http://apache.org/xml/features/xinclude/fixup-language).
     */
    protected static final String XINCLUDE_FIXUP_LANGUAGE_FEATURE_ID =
        "http://apache.org/xml/features/xinclude/fixup-language";

    // default settings

    /**
     * Default parser name (dom.wrappers.Xerces).
     */
    protected static final String DEFAULT_PARSER_NAME = "dom.wrappers.Xerces";

    /**
     * Default namespaces support (true).
     */
    protected static final boolean DEFAULT_NAMESPACES = true;

    /**
     * Default validation support (false).
     */
    protected static final boolean DEFAULT_VALIDATION = true;

    /**
     * Default Schema validation support (false).
     */
    protected static final boolean DEFAULT_SCHEMA_VALIDATION = true;

    /**
     * Default Schema full checking support (false).
     */
    protected static final boolean DEFAULT_SCHEMA_FULL_CHECKING = true;

    /**
     * Default honour all schema locations (false).
     */
    protected static final boolean DEFAULT_HONOUR_ALL_SCHEMA_LOCATIONS = true;

    /**
     * Default validate schema annotations (false).
     */
    protected static final boolean DEFAULT_VALIDATE_ANNOTATIONS = false;

    /**
     * Default dynamic validation support (false).
     */
    protected static final boolean DEFAULT_DYNAMIC_VALIDATION = false;

    /**
     * Default XInclude processing support (false).
     */
    protected static final boolean DEFAULT_XINCLUDE = false;

    /**
     * Default XInclude fixup base URIs support (true).
     */
    protected static final boolean DEFAULT_XINCLUDE_FIXUP_BASE_URIS = true;

    /**
     * Default XInclude fixup language support (true).
     */
    protected static final boolean DEFAULT_XINCLUDE_FIXUP_LANGUAGE = true;

    private static final ParserWrapper parserInstance = getParser();

    private DomReader()
    {
    }

    private static ParserWrapper getParser()
    {
        ParserWrapper parser;
        boolean namespaces = DEFAULT_NAMESPACES;
        boolean validation = DEFAULT_VALIDATION;
        boolean schemaValidation = DEFAULT_SCHEMA_VALIDATION;
        boolean schemaFullChecking = DEFAULT_SCHEMA_FULL_CHECKING;
        boolean honourAllSchemaLocations = DEFAULT_HONOUR_ALL_SCHEMA_LOCATIONS;
        boolean validateAnnotations = DEFAULT_VALIDATE_ANNOTATIONS;
        boolean dynamicValidation = DEFAULT_DYNAMIC_VALIDATION;
        boolean xincludeProcessing = DEFAULT_XINCLUDE;
        boolean xincludeFixupBaseURIs = DEFAULT_XINCLUDE_FIXUP_BASE_URIS;
        boolean xincludeFixupLanguage = DEFAULT_XINCLUDE_FIXUP_LANGUAGE;

        // create parser
        try
        {
            parser = (ParserWrapper)Class.forName( DEFAULT_PARSER_NAME ).newInstance();
        }
        catch ( Exception e )
        {
            System.err.println(
                "error: Unable to instantiate parser (" + DEFAULT_PARSER_NAME + ")" );
            return null;
        }
        // set parser features
        try
        {
            parser.setFeature( NAMESPACES_FEATURE_ID, namespaces );
        }
        catch ( SAXException e )
        {
            System.err.println( "warning: Parser does not support feature ("
                + NAMESPACES_FEATURE_ID + ")" );
        }
        try
        {
            parser.setFeature( VALIDATION_FEATURE_ID, validation );
        }
        catch ( SAXException e )
        {
            System.err.println( "warning: Parser does not support feature ("
                + VALIDATION_FEATURE_ID + ")" );
        }
        try
        {
            parser.setFeature( SCHEMA_VALIDATION_FEATURE_ID, schemaValidation );
        }
        catch ( SAXException e )
        {
            System.err.println( "warning: Parser does not support feature ("
                + SCHEMA_VALIDATION_FEATURE_ID + ")" );
        }
        try
        {
            parser.setFeature( SCHEMA_FULL_CHECKING_FEATURE_ID, schemaFullChecking );
        }
        catch ( SAXException e )
        {
            System.err.println( "warning: Parser does not support feature ("
                + SCHEMA_FULL_CHECKING_FEATURE_ID + ")" );
        }
        try
        {
            parser.setFeature( HONOUR_ALL_SCHEMA_LOCATIONS_ID, honourAllSchemaLocations );
        }
        catch ( SAXException e )
        {
            System.err.println( "warning: Parser does not support feature ("
                + HONOUR_ALL_SCHEMA_LOCATIONS_ID + ")" );
        }
        try
        {
            parser.setFeature( VALIDATE_ANNOTATIONS_ID, validateAnnotations );
        }
        catch ( SAXException e )
        {
            System.err.println( "warning: Parser does not support feature ("
                + VALIDATE_ANNOTATIONS_ID + ")" );
        }
        try
        {
            parser.setFeature( DYNAMIC_VALIDATION_FEATURE_ID, dynamicValidation );
        }
        catch ( SAXException e )
        {
            System.err.println( "warning: Parser does not support feature ("
                + DYNAMIC_VALIDATION_FEATURE_ID + ")" );
        }
        try
        {
            parser.setFeature( XINCLUDE_FEATURE_ID, xincludeProcessing );
        }
        catch ( SAXException e )
        {
            System.err.println( "warning: Parser does not support feature ("
                + XINCLUDE_FEATURE_ID + ")" );
        }
        try
        {
            parser.setFeature( XINCLUDE_FIXUP_BASE_URIS_FEATURE_ID, xincludeFixupBaseURIs );
        }
        catch ( SAXException e )
        {
            System.err.println( "warning: Parser does not support feature ("
                + XINCLUDE_FIXUP_BASE_URIS_FEATURE_ID + ")" );
        }

        try
        {
            parser.setFeature( XINCLUDE_FIXUP_LANGUAGE_FEATURE_ID, xincludeFixupLanguage );
        }
        catch ( SAXException e )
        {
            System.err.println( "warning: Parser does not support feature ("
                + XINCLUDE_FIXUP_LANGUAGE_FEATURE_ID + ")" );
        }
        return parser;
    }

    public static Document getLSDocument( File inputXmlFile ) throws FileNotFoundException
    {
        final DOMImplementationLS impl;
        LSParser parser;

        System.setProperty( DOMImplementationRegistry.PROPERTY,
            "org.apache.xerces.dom.DOMImplementationSourceImpl" );

        try
        {
            DOMImplementationRegistry registry =
                DOMImplementationRegistry.newInstance();

            impl = (DOMImplementationLS)registry.getDOMImplementation( "LS" );

            parser = impl.createLSParser(
                DOMImplementationLS.MODE_SYNCHRONOUS, null );
        }
        catch ( Exception e )
        {
            System.err.println( e.getMessage() );
            return null;
        }

//        parser.getDomConfig().setParameter( "resource-resolver",
//            new XMLCatalogResolver() );

        parser.getDomConfig().setParameter( "resource-resolver", new LSResourceResolver()
        {
            public LSInput resolveResource( String type, String namespaceURI, String publicId, String systemId,
                String baseURI )
            {
                System.out.println( "Resolving resource: " + namespaceURI );
                if ( namespaceURI.equals( "http://www.gettestfire.org/schema/base.xsd" ) )
                {
                    LSInput ret = impl.createLSInput();
                    FileInputStream is = null;
                    try
                    {
                        is = new FileInputStream( (new URL( systemId )).getPath() );
                    }
                    catch ( IOException e )
                    {
                        e.printStackTrace();
                        System.exit( 1 );
                    }
                    ret.setByteStream( is );
                    ret.setSystemId( systemId );
                    return ret;
                }
                return null;
            }
        }
        );

        LSInput input = impl.createLSInput();
        LSOutput output = impl.createLSOutput();
        input.setByteStream( new FileInputStream( inputXmlFile ) );
        output.setByteStream( System.out );
        Document doc = parser.parse( input );

        return doc;

    }

    public static Document getDocument( File xmlFile ) throws Exception
    {
        return parserInstance.parse( xmlFile.getCanonicalFile().toURI().toString() );
    }

    public static Document getDocument( CharSequence fullyQualifiedDocAsCharSequence ) throws Exception
    {
        File xmlFile = createTempFileForXMLString( fullyQualifiedDocAsCharSequence );

        Document doc = getDocument( xmlFile );
        xmlFile.delete();
        return doc;
    }

    public static Document getLSDocument( CharSequence fullyQualifiedDocAsCharSequence ) throws Exception
    {
        File xmlFile = createTempFileForXMLString( fullyQualifiedDocAsCharSequence );
        Document doc = readIn( xmlFile );
        xmlFile.delete();
        return doc;
    }

    public static File createTempFileForXMLString( CharSequence input ) throws IOException
    {
        File outFile = File.createTempFile( "testfire_", ".xml" );
        ByteBuffer b = ByteBuffer.wrap( input.toString().getBytes() );
        RandomAccessFile outStream = new RandomAccessFile( outFile, "rw" );
        FileChannel outChan = outStream.getChannel();
        outChan.write( b );
        outChan.close();
        outStream.close();
        return outFile;
    }

    public static Document readIn( File inputXmlFile )
    {

        try
        {
            System.setProperty( DOMImplementationRegistry.PROPERTY,
                "org.apache.xerces.dom.DOMImplementationSourceImpl" );
            DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
            final DOMImplementationLS impl = (DOMImplementationLS)registry.getDOMImplementation( "LS" );
            LSParser parser = impl.createLSParser(
                DOMImplementationLS.MODE_SYNCHRONOUS, null );
            LSSerializer domWriter = impl.createLSSerializer();
            DOMConfiguration config = domWriter.getDomConfig();
            // create Error Handler
            DOMErrorHandler errorHandler = new XMLErrorHandler();
            config.setParameter( "error-handler", errorHandler);
            config.setParameter( "resource-resolver", new LSResourceResolver()
        {
            public LSInput resolveResource( String type, String namespaceURI, String publicId, String systemId,
                String baseURI )
            {
                System.out.println( "Resolving resource: " + namespaceURI );
                if ( namespaceURI.equals( "http://www.gettestfire.org/schema/base.xsd" ) )
                {
                    LSInput ret = impl.createLSInput();
                    FileInputStream is = null;
                    try
                    {
                        is = new FileInputStream( (new URL( systemId )).getPath() );
                    }
                    catch ( IOException e )
                    {
                        e.printStackTrace();
                        System.exit( 1 );
                    }
                    ret.setByteStream( is );
                    ret.setSystemId( systemId );
                    return ret;
                }
                return null;
            }
        }
        );

            // set validation feature
            config.setParameter( "validate", true );
            LSInput input = impl.createLSInput();
            input.setByteStream( new FileInputStream( inputXmlFile ) );
            Document doc = parser.parse( input );

            return doc;
        }
        catch ( Exception e )
        {
            throw new IllegalStateException( e );
        }


    }

    public static String writeOut( Node node )
    {

        try
        {
            System.setProperty( DOMImplementationRegistry.PROPERTY,
                "org.apache.xerces.dom.DOMImplementationSourceImpl" );
            DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
            DOMImplementationLS impl = (DOMImplementationLS)registry.getDOMImplementation( "LS" );
            LSSerializer domWriter = impl.createLSSerializer();
            DOMConfiguration config = domWriter.getDomConfig();
            // create Error Handler
            DOMErrorHandler errorHandler = new XMLErrorHandler();

            // set error handler on the Document
            config = node.getOwnerDocument().getDomConfig();

            config.setParameter( "error-handler", errorHandler );

            // set validation feature
            config.setParameter( "validate", Boolean.FALSE );

            node.normalize();

            // serialize document to standard output
            //domWriter.writeNode(System.out, doc);
            LSOutput dOut = new DOMOutputImpl();
            ByteArrayOutputStream fos = new ByteArrayOutputStream();
            dOut.setByteStream( fos );
            domWriter.write( node, dOut );
            return fos.toString();
        }
        catch ( Exception e )
        {
            throw new IllegalStateException( e );
        }


    }

    private static class XMLErrorHandler implements DOMErrorHandler
    {

        protected DOMError error;

        public XMLErrorHandler()
        {
        }

        public boolean handleError( DOMError error )
        {
            this.error = error;
            return true;
        }

        public DOMError getError()
        {
            if ( error != null )
            {
                System.out.println( error.getMessage() );
            }

            return error;
        }
    }


}

// eof: DomReader.java